Module Imports¶

In [1]:
import os
import pandas as pd

import numpy as np

import matplotlib.pyplot as plt
import seaborn as sns

import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Rescaling, Conv2D, MaxPooling2D, Dense, Flatten, Dropout, BatchNormalization
from tensorflow.keras.preprocessing.image import ImageDataGenerator
from tensorflow.keras.optimizers import Adam

from sklearn.metrics import confusion_matrix, precision_score, recall_score, accuracy_score, f1_score

Data import and formatting¶

In [2]:
# location path of the datasets
train_dir = "/Users/preslav/Downloads/cw_cop528/imageset/train"
test_dir = "/Users/preslav/Downloads/cw_cop528/imageset/val"
In [3]:
# setting a common standard for the pixel values, to fall in 
# setting a validation and training split, alongside augmentation
# details about the train dataset
train_data = ImageDataGenerator(rescale=1./255,
                               rotation_range=40,
                               shear_range=0.2,
                               zoom_range=0.3,
                               horizontal_flip=True,
                               fill_mode="nearest",
                               width_shift_range=0.3,
                               height_shift_range=0.3,
                               validation_split=0.2)
val_data = ImageDataGenerator(rescale=1/255, 
                              validation_split=0.2)
test_data = ImageDataGenerator(rescale=1./255)
In [4]:
# importing the data batches and setting their properties 
train_batches = train_data.flow_from_directory(directory = train_dir, 
                                               target_size = (224, 224), 
                                               subset = "training",
                                               batch_size = 32, 
                                               seed = 2)
validation_batches = val_data.flow_from_directory(directory = train_dir, 
                                                  target_size = (224, 224), 
                                                  subset = "validation",
                                                  batch_size = 32, 
                                                  seed = 2)
test_batches = test_data.flow_from_directory(directory = test_dir, 
                                             target_size = (224, 224),
                                             batch_size = 32, 
                                             shuffle = False)
Found 7578 images belonging to 10 classes.
Found 1891 images belonging to 10 classes.
Found 3925 images belonging to 10 classes.
In [5]:
# import of the class labels names and their total number 
class_names = list(train_batches.class_indices.keys())
num_classes = len(class_names)
print(class_names)
print(num_classes)
['building', 'dog', 'fish', 'gas_station', 'golf', 'musician', 'parachute', 'radio', 'saw', 'vehicle']
10

Visualizing few "distored" images¶

In [6]:
# importing a batch of images and labels
img, lbl = next(train_batches)
In [7]:
# plotting 9 images and their respective class labels
plt.figure(figsize = (12, 12))
for i in range(9):
    class_index = np.argmax(lbl[i])
    plt.subplot(3, 3, i + 1)
    plt.imshow(img[i])
    plt.title(class_names[class_index])
    plt.axis("off")
plt.tight_layout()
plt.show()

Building Model Architecture¶

In [8]:
# setting the model's architecture
model_adapted_augmented = Sequential([
    Conv2D(16, (3,3), 1, activation="relu"),
    BatchNormalization(),
    MaxPooling2D(),
    Conv2D(32, (3,3), 1, activation="relu"),
    BatchNormalization(),
    Dropout(0.25),
    Conv2D(32, (3,3), 1, activation="relu"),
    BatchNormalization(),
    Conv2D(32, (3,3), 1, activation="relu"),
    BatchNormalization(),
    MaxPooling2D(),
    Conv2D(32, (3,3), 1, activation="relu"),
    BatchNormalization(),
    Dropout(0.25),
    Conv2D(32, (3,3), 1, activation="relu"),
    BatchNormalization(),
    MaxPooling2D(),
    Flatten(),
    Dense(256, activation="relu"),
    BatchNormalization(),
    Dropout(0.5),
    Dense(num_classes, activation="softmax")
])
Metal device set to: Apple M2
2023-03-17 11:43:26.503498: I tensorflow/core/common_runtime/pluggable_device/pluggable_device_factory.cc:305] Could not identify NUMA node of platform GPU ID 0, defaulting to 0. Your kernel may not have been built with NUMA support.
2023-03-17 11:43:26.503609: I tensorflow/core/common_runtime/pluggable_device/pluggable_device_factory.cc:271] Created TensorFlow device (/job:localhost/replica:0/task:0/device:GPU:0 with 0 MB memory) -> physical PluggableDevice (device: 0, name: METAL, pci bus id: <undefined>)
In [9]:
# setting the model's loss function, gradient descnet optimizer and evaluation metrics 
model_adapted_augmented.compile(optimizer = "adam", loss = "categorical_crossentropy", metrics = ["accuracy"])
In [10]:
# performing training of the model with the training batches and validaiton batches
epochs = 20
history_adapted_augmented = model_adapted_augmented.fit(train_batches,
                      validation_data = validation_batches,
                      epochs = epochs)
Epoch 1/20
2023-03-17 11:43:27.095353: W tensorflow/core/platform/profile_utils/cpu_utils.cc:128] Failed to get CPU frequency: 0 Hz
2023-03-17 11:43:27.564403: I tensorflow/core/grappler/optimizers/custom_graph_optimizer_registry.cc:113] Plugin optimizer for device_type GPU is enabled.
237/237 [==============================] - ETA: 0s - loss: 2.2734 - accuracy: 0.2666
2023-03-17 11:44:15.094136: I tensorflow/core/grappler/optimizers/custom_graph_optimizer_registry.cc:113] Plugin optimizer for device_type GPU is enabled.
237/237 [==============================] - 52s 214ms/step - loss: 2.2734 - accuracy: 0.2666 - val_loss: 7.9458 - val_accuracy: 0.1111
Epoch 2/20
237/237 [==============================] - 51s 215ms/step - loss: 1.9513 - accuracy: 0.3394 - val_loss: 18.5200 - val_accuracy: 0.1512
Epoch 3/20
237/237 [==============================] - 51s 214ms/step - loss: 1.8802 - accuracy: 0.3580 - val_loss: 2.0102 - val_accuracy: 0.3887
Epoch 4/20
237/237 [==============================] - 50s 212ms/step - loss: 1.7232 - accuracy: 0.4212 - val_loss: 2.5158 - val_accuracy: 0.3210
Epoch 5/20
237/237 [==============================] - 50s 212ms/step - loss: 1.6860 - accuracy: 0.4274 - val_loss: 1.6564 - val_accuracy: 0.4870
Epoch 6/20
237/237 [==============================] - 50s 212ms/step - loss: 1.5740 - accuracy: 0.4702 - val_loss: 1.5357 - val_accuracy: 0.5331
Epoch 7/20
237/237 [==============================] - 51s 213ms/step - loss: 1.5051 - accuracy: 0.4950 - val_loss: 2.0012 - val_accuracy: 0.4601
Epoch 8/20
237/237 [==============================] - 51s 213ms/step - loss: 1.4504 - accuracy: 0.5182 - val_loss: 1.3038 - val_accuracy: 0.5838
Epoch 9/20
237/237 [==============================] - 51s 213ms/step - loss: 1.3965 - accuracy: 0.5376 - val_loss: 2.9507 - val_accuracy: 0.4812
Epoch 10/20
237/237 [==============================] - 50s 212ms/step - loss: 1.3798 - accuracy: 0.5383 - val_loss: 2.5158 - val_accuracy: 0.3845
Epoch 11/20
237/237 [==============================] - 50s 213ms/step - loss: 1.3507 - accuracy: 0.5414 - val_loss: 1.4932 - val_accuracy: 0.5056
Epoch 12/20
237/237 [==============================] - 51s 212ms/step - loss: 1.3166 - accuracy: 0.5652 - val_loss: 2.1749 - val_accuracy: 0.5056
Epoch 13/20
237/237 [==============================] - 50s 212ms/step - loss: 1.2959 - accuracy: 0.5681 - val_loss: 3.2859 - val_accuracy: 0.3982
Epoch 14/20
237/237 [==============================] - 50s 212ms/step - loss: 1.2866 - accuracy: 0.5744 - val_loss: 1.9545 - val_accuracy: 0.5278
Epoch 15/20
237/237 [==============================] - 51s 213ms/step - loss: 1.2399 - accuracy: 0.5900 - val_loss: 2.1397 - val_accuracy: 0.4246
Epoch 16/20
237/237 [==============================] - 51s 213ms/step - loss: 1.2264 - accuracy: 0.5897 - val_loss: 1.4010 - val_accuracy: 0.5690
Epoch 17/20
237/237 [==============================] - 50s 212ms/step - loss: 1.2528 - accuracy: 0.5830 - val_loss: 2.4072 - val_accuracy: 0.4146
Epoch 18/20
237/237 [==============================] - 51s 213ms/step - loss: 1.2449 - accuracy: 0.5900 - val_loss: 1.3345 - val_accuracy: 0.5785
Epoch 19/20
237/237 [==============================] - 50s 212ms/step - loss: 1.2054 - accuracy: 0.6002 - val_loss: 1.5779 - val_accuracy: 0.6621
Epoch 20/20
237/237 [==============================] - 51s 214ms/step - loss: 1.1713 - accuracy: 0.6134 - val_loss: 1.8299 - val_accuracy: 0.5193
In [11]:
# getting model's summary
model_adapted_augmented.summary()
Model: "sequential"
_________________________________________________________________
 Layer (type)                Output Shape              Param #   
=================================================================
 conv2d (Conv2D)             (None, None, None, 16)    448       
                                                                 
 batch_normalization (BatchN  (None, None, None, 16)   64        
 ormalization)                                                   
                                                                 
 max_pooling2d (MaxPooling2D  (None, None, None, 16)   0         
 )                                                               
                                                                 
 conv2d_1 (Conv2D)           (None, None, None, 32)    4640      
                                                                 
 batch_normalization_1 (Batc  (None, None, None, 32)   128       
 hNormalization)                                                 
                                                                 
 dropout (Dropout)           (None, None, None, 32)    0         
                                                                 
 conv2d_2 (Conv2D)           (None, None, None, 32)    9248      
                                                                 
 batch_normalization_2 (Batc  (None, None, None, 32)   128       
 hNormalization)                                                 
                                                                 
 conv2d_3 (Conv2D)           (None, None, None, 32)    9248      
                                                                 
 batch_normalization_3 (Batc  (None, None, None, 32)   128       
 hNormalization)                                                 
                                                                 
 max_pooling2d_1 (MaxPooling  (None, None, None, 32)   0         
 2D)                                                             
                                                                 
 conv2d_4 (Conv2D)           (None, None, None, 32)    9248      
                                                                 
 batch_normalization_4 (Batc  (None, None, None, 32)   128       
 hNormalization)                                                 
                                                                 
 dropout_1 (Dropout)         (None, None, None, 32)    0         
                                                                 
 conv2d_5 (Conv2D)           (None, None, None, 32)    9248      
                                                                 
 batch_normalization_5 (Batc  (None, None, None, 32)   128       
 hNormalization)                                                 
                                                                 
 max_pooling2d_2 (MaxPooling  (None, None, None, 32)   0         
 2D)                                                             
                                                                 
 flatten (Flatten)           (None, None)              0         
                                                                 
 dense (Dense)               (None, 256)               4718848   
                                                                 
 batch_normalization_6 (Batc  (None, 256)              1024      
 hNormalization)                                                 
                                                                 
 dropout_2 (Dropout)         (None, 256)               0         
                                                                 
 dense_1 (Dense)             (None, 10)                2570      
                                                                 
=================================================================
Total params: 4,765,226
Trainable params: 4,764,362
Non-trainable params: 864
_________________________________________________________________

Evaluating Performance¶

Graphical evaluation¶

In [12]:
# Graphical evaluation of training performance 
acc = history_adapted_augmented.history['accuracy']
val_acc = history_adapted_augmented.history['val_accuracy']

loss = history_adapted_augmented.history['loss']
val_loss = history_adapted_augmented.history['val_loss']

epochs_range = range(epochs)

plt.figure(figsize=(11, 8))
plt.subplots_adjust(hspace = .3)
plt.subplot(2, 1, 1)
plt.plot(epochs_range, acc, label = 'Training Accuracy', color = "orange")
plt.plot(epochs_range, val_acc, label = 'Validation Accuracy', color = "blue")
plt.legend(loc = 'best')
plt.xlabel('Epochs')
plt.title('Training and Validation Accuracy', size = 13)

plt.subplot(2, 1, 2)
plt.plot(epochs_range, loss, label = 'Training Loss', color = "orange")
plt.plot(epochs_range, val_loss, label = 'Validation Loss', color = "blue")
plt.legend(loc = 'best')
plt.title('Training and Validation Loss', size = 13)
plt.ylim(0, 3.5)
plt.xlabel('Epochs')

plt.suptitle("Base Model's Updated Architecture with Data Augmentation", size=15)
plt.show()

Evaluating model's performance on the test dataset¶

In [13]:
# Test loss and accuracy measurments 
test_loss, test_acc = model_adapted_augmented.evaluate(test_batches)
print('Test loss:', test_loss)
print('Test accuracy:', test_acc)
123/123 [==============================] - 7s 60ms/step - loss: 1.7517 - accuracy: 0.5131
Test loss: 1.7516899108886719
Test accuracy: 0.5131210684776306

Evaluating the classification performance¶

i) via confussion matrix¶

In [14]:
# getting prediction labales by running the softmax results in argmax
test_labels = test_batches.classes
y_pred = model_adapted_augmented.predict(test_batches)
predicted_lables = np.argmax(y_pred, axis = 1)
cm =  confusion_matrix(test_labels, predicted_lables)
  1/123 [..............................] - ETA: 25s
2023-03-17 12:00:27.553762: I tensorflow/core/grappler/optimizers/custom_graph_optimizer_registry.cc:113] Plugin optimizer for device_type GPU is enabled.
123/123 [==============================] - 7s 58ms/step
In [15]:
# dataframe containing the confussion matrix
cfm = pd.DataFrame(cm, index = class_names, columns = class_names)
In [16]:
# plotting the conffusion matrix
sns.heatmap(cfm, annot=True, fmt='d', cmap='Purples')
plt.xlabel('Predicted Label')
plt.ylabel('True Label')
plt.xticks(rotation=78)
plt.title("Base Model's Updated Architecture with Data Augmentation", size=15)
plt.show()

ii) via designated classification performance evaluators¶

In [17]:
print("Preicision score:", precision_score(test_labels, predicted_lables, average="weighted"))
print("Recall score:", recall_score(test_labels, predicted_lables, average = "weighted"))
print("F1_score:", f1_score(test_labels, predicted_lables, average = "weighted"))
Preicision score: 0.5854724080172969
Recall score: 0.5131210191082802
F1_score: 0.5034306437499216

Testing the model's prediction onto actual images¶

In [18]:
# importing the test datest again, so that this time images can be shuffled
# so that displayed images are not ordered in the same way as in the dataset 
# and variety of classes can be examined
test_data_shuffled = tf.keras.utils.image_dataset_from_directory(test_dir, shuffle = True, seed = 247)
Found 3925 files belonging to 10 classes.
In [19]:
def right_format_image(pic):
    '''
    This function returns a 
    reshaped image into 224x224 
    format in terms of height and 
    width.
    Further it normalizes the 
    pixel values within the range
    of [0, 1].
    '''
    img_size = (224, 224)
    image = tf.image.resize(pic, img_size)
    image_expanded = np.expand_dims(image, axis=0)
    image_copy = np.copy(image_expanded)
    normalized = image_copy/255.
    return normalized
In [20]:
def data_iterator(data):
    '''
    This function returns as arrays the 
    components of a batch.
    '''
    iterator = data.as_numpy_iterator()
    batch = iterator.next()
    return batch
In [21]:
# plotting images from the test dataset, with their actual and predicted from the model labels 
predicted_batch = data_iterator(test_data_shuffled)

plt.figure(figsize=(12, 12))
plt.subplots_adjust(hspace = .1, wspace=.3)
plt.suptitle("Base Model's Updated Architecture with Data Augmentation", size = 20)
for i in range(9):
    image, label = predicted_batch[0][i], predicted_batch[1][i]
    predictions = model_adapted_augmented.predict(right_format_image(image))
    prediction_label = class_names[predictions.argmax()]
    ax = plt.subplot(3, 3, i + 1)
    plt.imshow(image.astype(np.uint8))
    plt.title("Actual label:{};\nPredicted label:{}".format(class_names[label],
                                                           class_names[predictions.argmax()]), size = 9)
    plt.axis("off")
1/1 [==============================] - 0s 113ms/step
1/1 [==============================] - 0s 10ms/step
1/1 [==============================] - 0s 8ms/step
1/1 [==============================] - 0s 8ms/step
1/1 [==============================] - 0s 7ms/step
2023-03-17 12:00:35.460283: I tensorflow/core/grappler/optimizers/custom_graph_optimizer_registry.cc:113] Plugin optimizer for device_type GPU is enabled.
1/1 [==============================] - 0s 9ms/step
1/1 [==============================] - 0s 7ms/step
1/1 [==============================] - 0s 8ms/step
1/1 [==============================] - 0s 8ms/step
In [ ]: